/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pkgUtil.pkgSupportFile.pkgFrameworkFile;

/**
 *Representa un manejador para un archivo con un Head y demás registros del mismo
 *tipo de dato. Por poseer este archivo un Head se lo considera como NO HOMOGENEO
 *respecto al tipo de dato que graba en sus registros.
 * 
 * @author Ayala Jorge Armando
 * @version Agosto de 2012
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class ManagerFile {

    private File fd;                   // descriptor del archivo para acceder a sus propiedades externas
    private RandomAccessFile maestro;  // objeto para acceder al contenido del archivo
    private IGrabable tipo;             // representa al contenido grabado en el archivo
    private IGrabable headDatos;        // los datos de encabezado del archivo
    private boolean headIsSave;

    /**
     * Crea un manejador para el Archivo, asociando al mismo un nombre a modo de
     * file descriptor, y un tipo de contenido al que quedará fijo. El segundo
     * parámetro se usa para fijar el tipo de registro que será aceptado para
     * grabar en el archivo. No se crea el archivo en disco, ni se abre. Sólo se
     * crea un descriptor general para él. La apertura y eventual creación, debe
     * hacerse con el método openForReadWrite().
     *
     * @param nombre es el nombre físico del archivo a crear
     * @param datos una instancia de la clase a la que pertenecen los objetos
     * cuyos datos serán grabados. La instancia referida por datos NO será
     * grabada en el archivo
     * @param headDatos una instancia de la clase a la que pertenecen los
     * objetos cuyos datos serán grabados en el encabezado del archivo.
     * @throws ClassNotFoundException si no se informa correctamente el tipo de
     * registro a grabar
     */
    public ManagerFile(String nombre, IGrabable datos, IGrabable headDatos) throws ClassNotFoundException {
        if (datos == null) {
            throw new ClassNotFoundException("Clase incorrecta o inexistente para el tipo de registro");
        }

        tipo = datos;
        this.headDatos = headDatos;
        headIsSave = false;
        fd = new File(nombre);
    }

    public ManagerFile(File file, IGrabable datos, IGrabable headDatos) throws ClassNotFoundException {
        if (datos == null) {
            throw new ClassNotFoundException("Clase incorrecta o inexistente para el tipo de registro");
        }

        tipo = datos;
        this.headDatos = headDatos;
        headIsSave = false;
        fd = file;
    }

    /**
     * @return the headDatos
     */
    public IGrabable getHeadDatos() {
        return headDatos;
    }

    /**
     * @param headDatos the headDatos to set
     */
    public void setHeadDatos(IGrabable headDatos) {
        this.headDatos = headDatos;
    }

    /**
     * Acceso al descriptor del archivo
     *
     * @return un objeto de tipo File con las propiedades de file system del
     * archivo
     */
    public File getFileDescriptor() {
        return fd;
    }

    /**
     * Acceso al manejador del archivo binario
     *
     * @return un objeto de tipo RandomAccessFile que permite acceder al bloque
     * físico de datos en disco, en forma directa
     */
    public RandomAccessFile getMasterFile() {
        return maestro;
    }

    /**
     * Acceso al descriptor de la clase del registro que se graba en el archivo
     *
     * @return una cadena con el nombre de la clase del registro usado en el
     * archivo
     */
    public String getRegisterType() {
        return tipo.getClass().getName();
    }

    /**
     * Borra el Archivo del disco
     */
    public void delete() {
        fd.delete();
    }

    /**
     * Cambia el nombre del archivo
     *
     * @param nuevo otro Archivo, cuyo nombre (o file descriptor) será dado al
     * actual
     */
    public void rename(ManagerFile nuevo) {
        fd.renameTo(nuevo.fd);
    }

    /**
     * Abre el archivo en modo de sólo lectura. El archivo en disco debe existir
     * previamente. Queda posicionado al principio del archivo.
     */
    public void openForRead() throws FileNotFoundException {
        maestro = new RandomAccessFile(fd, "r");
    }

    /**
     * Abre el archivo en modo de lectura y grabación. Si el archivo en disco no
     * existía, será creado. Si existía, será posicionado al principio del
     * archivo. Mueva el puntero de registro activo con el método seekRegister()
     * o con seekByte().
     */
    public void openForReadWrite() throws FileNotFoundException {
        maestro = new RandomAccessFile(fd, "rw");
    }

    /**
     * Cierra el archivo
     */
    public void close() throws IOException {
        maestro.close();
    }

    /**
     * Rebobina el archivo: ubica el puntero de registro activo en la posición
     * cero
     */
    public void rewind() throws IOException {
        maestro.seek(0);
    }

    /**
     * Devuelve el número de byte en el cual esta posicionado el archivo en este
     * momento
     *
     * @return el número de byte de posicionamiento actual
     */
    public long bytePos() throws IOException {
        return maestro.getFilePointer();
    }

    /**
     * Posiciona el puntero de registro activo al final del archivo
     */
    public void goFinal() throws IOException {
        maestro.seek(maestro.length());
    }

    /**
     * Determina si se ha llegado al final del archivo o no
     *
     * @return true si se llegó al final - false en caso contrario
     * @throws IOException si hubo problema en la operación
     */
    public boolean eof() throws IOException {

        if (maestro.getFilePointer() < maestro.length()) {
            return false;
        } else {
            return true;
        }

    }

    /**
     * Graba un registro en el archivo
     *
     * @param r el registro a grabar
     */
    public void grabar(Register r) throws IOException {
        if (r != null && r.getData().getClass() == tipo.getClass()) {
            grabarHead();
            r.save(maestro);
        }
    }

    private void grabarHead() throws IOException {
        if (headDatos == null || headIsSave) {
            return;
        }
        Register regHead = new Register(headDatos);
        regHead.save(maestro);
        headIsSave = true;
    }

    /**
     * Lee un registro del archivo
     *
     * @return el registro leido
     * @throws IOException si hubo problema en la operación
     */
    public Register leer() throws InstantiationException, IllegalAccessException, IOException {
        Register r = new Register((IGrabable) tipo.getClass().newInstance());
        r.read(maestro);
        return r;
    }

    public Register leerHead() throws InstantiationException, IllegalAccessException, IOException {
        Register r = new Register((IGrabable) headDatos.getClass().newInstance());
        r.read(maestro);
        return r;
    }

    /**
     * Agrega un registro en el archivo, sin controlar repetición. La clase del
     * nuevo registro debe coincidir con la clase indicada para el archivo al
     * invocar al constructor. El archivo debe estar abierto en modo de
     * grabación.
     *
     * @param r registro a agregar
     * @return true si fue posible agregar el registro - false si no fue posible
     */
    public boolean altaDirecta(Register r) throws FileNotFoundException, IOException {
        boolean resp = false;

        if (r != null && tipo.getClass() == r.getData().getClass()) {
            openForReadWrite();

            goFinal();
            grabar(r);
            resp = true;

            close();
        }

        return resp;
    }
}
